﻿using System;
using System.Data;
using System.Linq;
using System.Reflection;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using PropertyExpression.Common;
using PropertyExpression.ControllableQuery.Toolkit;

namespace PropertyExpression.ControllableQuery.Demo
{
    [TestClass]
    public class DALTest
    {
        [TestMethod]
        public void CheckAllQueries()
        {
            QueryChecker.CheckAllQueries(
                ConnectionString,
                QueryResultPropertyTypeChecker,
                ParamCreator,
                QueryTypePredicate,
                info => true,
                ChoiceInvocationGetter,
                GetType().Assembly.GetTypes(),
                new DoNothingQRepository());
        }

        [TestMethod]
        public void FindUsages()
        {
            var repository = new FindUsagesQRepository();
            QueryChecker.CheckAllQueries(
                ConnectionString,
                QueryResultPropertyTypeChecker,
                ParamCreator,
                QueryTypePredicate,
                info => true,
                ChoiceInvocationGetter,
                GetType().Assembly.GetTypes(),
                repository);
            foreach (var qLocationString in
                repository.FindUsages(ConnectionString, tableName: "Post", columnName: "CreationDate"))
                Console.WriteLine(qLocationString);
        }

        [AssemblyInitialize]
        public static void AssemblyInit(TestContext context)
        {
            RazorInit.Register();
        }

        private static ChoiceInvocationGetter ChoiceInvocationGetter
        {
            get
            {
                return (type, context) => {
                    //if (typeof (IMy).Equals(type))
                    //    return typeof (IMy).Assembly.GetTypes().Where(type.IsAssignableFrom)
                    //        .SelectMany(_ => context.GetConstructorInvocations(_));

                    throw new ApplicationException(
                        string.Format("There are no rules to create instances of '{0}' type.", type));
                };
            }
        }

        private static Func<Type, bool> QueryTypePredicate
        {
            get { return type => true; }
        }

        private static ParamCreator ParamCreator
        {
            get
            {
                return type => {
                    var paramType = type.GetGenericArguments().Single();
                    if (typeof(string).Equals(paramType)) {
                        return Param.NewString();
                    }
                    else if (typeof(Option<string>).Equals(paramType)) {
                        return Param.NewOptionalString();
                    }
                    else if (typeof(Guid).Equals(paramType)) {
                        return Param.NewGuid();
                    }
                    else if (typeof(Option<Guid>).Equals(paramType)) {
                        return Param.NewOptionalGuid();
                    }
                    else if (typeof(int).Equals(paramType)) {
                        return Param.NewInt();
                    }
                    else if (typeof(Option<int>).Equals(paramType)) {
                        return Param.NewOptionalInt();
                    }
                    else if (typeof(DateTime).Equals(paramType)) {
                        return Param.NewDateTime();
                    }
                    else if (paramType.IsGenericTypeAndEquals(typeof(ITableValued<>))) {
                        return createTableValuedParamMethodInfo.MakeGenericMethod(
                            paramType.GetGenericArguments())
                            .Invoke(null, new object[] { });
                    }
                    else if (typeof(float).Equals(paramType)) {
                        return Param.NewFloat();
                    }
                    else if (typeof(bool).Equals(paramType)) {
                        return Param.NewBool();
                    }
                    else if (typeof(decimal).Equals(paramType)) {
                        return Param.NewDecimal();
                    }
                    else {
                        throw new InvalidOperationException();
                    }
                };
            }
        }

        private static readonly MethodInfo createTableValuedParamMethodInfo =
            QWriter.GetMethodInfo<Func<Param<ITableValued<object>>>>(() => Param.NewTableValued<object>()).GetGenericMethodDefinition();

        private static QueryResultPropertyTypeChecker QueryResultPropertyTypeChecker
        {
            get
            {
                return (propertyInfo, dataRow, queryNameFunc) => {
                    if (dataRow.GetDataTypeName().CompareSqlTypes("nvarchar")) {
                        CheckPropertyType<string>(propertyInfo, dataRow, queryNameFunc);
                    }
                    else if (dataRow.GetDataTypeName().CompareSqlTypes("varchar")) {
                        CheckPropertyType<string>(propertyInfo, dataRow, queryNameFunc);
                    }
                    else if (dataRow.GetDataTypeName().CompareSqlTypes("char")) {
                        CheckPropertyType<string>(propertyInfo, dataRow, queryNameFunc);
                    }
                    else if (dataRow.GetDataTypeName().CompareSqlTypes("nchar"))
                    {
                        CheckPropertyType<string>(propertyInfo, dataRow, queryNameFunc);
                    }
                    else if (dataRow.GetDataTypeName().CompareSqlTypes("uniqueidentifier"))
                    {
                        CheckPropertyType<Guid>(propertyInfo, dataRow, queryNameFunc);
                    }
                    else if (dataRow.GetDataTypeName().CompareSqlTypes("int")) {
                        CheckPropertyType<int>(propertyInfo, dataRow, queryNameFunc);
                    }
                    else if (dataRow.GetDataTypeName().CompareSqlTypes("bigint"))
                    {
                        CheckPropertyType<long>(propertyInfo, dataRow, queryNameFunc);
                    }
                    else if (dataRow.GetDataTypeName().CompareSqlTypes("smallint"))
                    {
                        CheckPropertyType<int>(propertyInfo, dataRow, queryNameFunc);
                    }
                    else if (dataRow.GetDataTypeName().CompareSqlTypes("money")) {
                        CheckPropertyType<decimal>(propertyInfo, dataRow, queryNameFunc);
                    }
                    else if (dataRow.GetDataTypeName().CompareSqlTypes("decimal")) {
                        CheckPropertyType<decimal>(propertyInfo, dataRow, queryNameFunc);
                    }
                    else if (dataRow.GetDataTypeName().CompareSqlTypes("datetime")) {
                        CheckPropertyType<DateTime>(propertyInfo, dataRow, queryNameFunc);
                    }
                    else if (dataRow.GetDataTypeName().CompareSqlTypes("bit")) {
                        CheckPropertyType<bool>(propertyInfo, dataRow, queryNameFunc);
                    }
                    else if (dataRow.GetDataTypeName().CompareSqlTypes("float")) {
                        CheckPropertyType<float>(propertyInfo, dataRow, queryNameFunc);
                    }
                    else if (dataRow.GetDataTypeName().CompareSqlTypes("timestamp")) {
                        CheckPropertyType<byte[]>(propertyInfo, dataRow, queryNameFunc);
                    }
                    else {
                        throw new InvalidOperationException();
                    }
                };
            }
        }

        private static void CheckPropertyType<T>(Type propertyType, DataRow dataRow, Func<string> queryNameFunc)
        {
            if (typeof(T).Equals(propertyType) && dataRow.GetAllowDbNull() == false) {
                //no-op
            }
            else {
                if (dataRow.GetAllowDbNull() == true && propertyType.IsClass) {
                    //no-op
                }
                else {
                    if (dataRow.GetAllowDbNull() == true
                        && propertyType.IsGenericTypeAndEquals(typeof(Nullable<>))
                        && typeof(T).Equals(propertyType.GetGenericArguments().Single())) {
                        //no-op
                    }
                    else {
                        throw new QueryCheckException(
                            string.Format(
                                @"The type of query result property was incorrect ColumnName = '{0}'. Expected SQL Server Database Engine type DataType = '{1}', AllowDBNull = '{2}' but was .NET Framework type '{3}'.
QueryType = '{4}'",
                                dataRow.GetColumnName(),
                                dataRow.GetDataTypeName(),
                                dataRow.GetAllowDbNull(),
                                propertyType,
                                queryNameFunc()));
                    }
                }
            }
        }

        public static string ConnectionString
        {
            get { return @"Data Source=(local)\SQL2012;Initial Catalog=Serverfault;Integrated Security=True"; }
        }
    }
}